<?php declare(strict_types=1);
/**
 * @author      xianganyall <xianganyall@gmail.com>
 * @copyright   2023-2025 owner
 **/

namespace Srv\Libs\Frame;

use Srv\Libs\Common\CommString;
use Swoole\Table;

final class WebSocketTable
{
    private static ?Table $wsConnTable                      = null;             // WS连接表
    private const WS_TABLE_ROWS_MAX                         = 1024*10;          // 同时最大连接数
    private const WS_TABLE_PREFIX                           = '_';              // WS连接的前缀
    private const WS_FIELD_FD                               = 'fd';             // fd
    private const WS_FIELD_TIME                             = 'time';           // time
    private const WS_FIELD_UID                              = 'uid';            // uid
    private const WS_FIELD_ENV                              = 'env';            // env
    private const WS_FIELD_GROUP                            = 'group';          // group
    private const WS_FIELD_LOGIN_KEY                        = 'tokenKey';       // tokenKey
    private const WS_FIELD_LOGIN_EXP_TIME                   = 'tokenTime';      // tokenTime
    private const WS_FIELD_LOGIN_PLATFORM                   = 'tokenPlatform';  // tokenPlatform
    private const WS_FIELD_LOGIN_DEVID                      = 'tokenDevId';     // tokenDevId
    private const WS_FIELD_LOGIN_AUTH                       = 'tokenAuth';      // tokenAuth
    private const WS_FIELD_LANG_TYPE                        = 'langType';       // langType
    private const WS_UID_LENGTH                             = 16;               // 用户Id长度
    private const WS_ENV_LENGTH                             = 1024*4;           // env内容长度[4K]
    private const WS_GROUP_LENGTH                           = 16;               // 组长度
    private const WS_LOGIN_KEY_LENGTH                       = 16;               // 登入令牌长度
    private const WS_LOGIN_PLATFORM_LENGTH                  = 32;               // 登入设备平台长度
    private const WS_LOGIN_DEVID_LENGTH                     = 36;               // 登入设备Id长度
    private const WS_LOGIN_AUTH_LENGTH                      = 1024;             // 登入认证长度[1K]
    private const WS_LANG_TYPE_LENGTH                       = 8;                // 设备语言长度

    private static ?Table $configTable                      = null;             // 配置表
    private const CF_TABLE_ROWS_MAX                         = 128;              // 配置最大个数
    private const CF_FIELD_NUM                              = 'num';            // 表字段-配置数
    private const CF_FIELD_STR                              = 'str';            // 表字段-配置内容
    private const CF_STR_LENGTH                             = 1024;             // 配置内容最大长度
    private const CF_KEY_SUB_KEY_ID                         = 'subKeyId';       // 订阅KeyId存储KEY
    private const CF_KEY_ACK_NUM                            = 'ackNum';         // WebSocket应答数

    private static ?Table $subKeyTable                      = null;             // 订阅Key表
    private const SK_TABLE_ROWS_MAX                         = 1024*10;          // 订阅Key最大个数
    private const SK_TABLE_ROWS_GC_MIN                      = 1024*1;           // 订阅KeyGC最小个数
    private const SK_FIELD_ID                               = 'id';             // 表字段-订阅KeyId
    private const SK_FIELD_NUM                              = 'num';            // 表字段-关联次数
    private const SK_SUB_KEY_LENGTH                         = 32;               // 订阅Key最大长度

    private static ?Table $subFdTable                       = null;             // 订阅Fd表[FD与SK关系]
    private const SF_TABLE_ROWS_MAX                         = 1024*10;          // 订阅Fd最大个数

    private static ?Table $ackNumTable                      = null;             // 应答数表
    private const AN_TABLE_ROWS_MAX                         = 1024*10;          // 应答数最大个数
    private const AN_FIELD_EXPIRE_TIME                      = 'expireTime';     // 过期时间[秒]

    /**
     * @return void
     * init
     */
    public static function init():void
    {
        if(!(self::$wsConnTable instanceof Table)){
            self::$wsConnTable                                  = new Table(self::WS_TABLE_ROWS_MAX);
            self::$wsConnTable->column(self::WS_FIELD_FD,               Table::TYPE_INT, 8);                                    // 网络fd
            self::$wsConnTable->column(self::WS_FIELD_TIME,             Table::TYPE_INT, 8);                                    // 连接时间[秒]
            self::$wsConnTable->column(self::WS_FIELD_UID,              Table::TYPE_STRING, self::WS_UID_LENGTH);               // 用户Id
            self::$wsConnTable->column(self::WS_FIELD_ENV,              Table::TYPE_STRING, self::WS_ENV_LENGTH);               // env内容
            self::$wsConnTable->column(self::WS_FIELD_GROUP,            Table::TYPE_STRING, self::WS_GROUP_LENGTH);             // 组
            self::$wsConnTable->column(self::WS_FIELD_LOGIN_KEY,        Table::TYPE_STRING, self::WS_LOGIN_KEY_LENGTH);         // 登入令牌
            self::$wsConnTable->column(self::WS_FIELD_LOGIN_EXP_TIME,   Table::TYPE_INT, 8);                                    // 令牌有效期时间[秒]
            self::$wsConnTable->column(self::WS_FIELD_LOGIN_PLATFORM,   Table::TYPE_STRING, self::WS_LOGIN_PLATFORM_LENGTH);    // 登入设备平台
            self::$wsConnTable->column(self::WS_FIELD_LOGIN_DEVID,      Table::TYPE_STRING, self::WS_LOGIN_DEVID_LENGTH);       // 登入设备Id
            self::$wsConnTable->column(self::WS_FIELD_LOGIN_AUTH,       Table::TYPE_STRING, self::WS_LOGIN_AUTH_LENGTH);        // 登入认证
            self::$wsConnTable->column(self::WS_FIELD_LANG_TYPE,        Table::TYPE_STRING, self::WS_LANG_TYPE_LENGTH);         // 设备语言
            self::$wsConnTable->create();
        }
        if(!(self::$configTable instanceof Table)){
            self::$configTable                                  = new Table(self::CF_TABLE_ROWS_MAX);
            self::$configTable->column(self::CF_FIELD_NUM,              Table::TYPE_INT, 8);                                    // 配置数
            self::$configTable->column(self::CF_FIELD_STR,              Table::TYPE_STRING, self::CF_STR_LENGTH);               // 配置内容
            self::$configTable->create();
        }
        if(!(self::$subKeyTable instanceof Table)){
            self::$subKeyTable                                  = new Table(self::SK_TABLE_ROWS_MAX);
            self::$subKeyTable->column(self::SK_FIELD_ID,               Table::TYPE_INT, 8);                                    // 订阅KeyId
            self::$subKeyTable->column(self::SK_FIELD_NUM,              Table::TYPE_INT, 4);                                    // 关联次数[0-则会被GC回收]
            self::$subKeyTable->create();
        }
        if(!(self::$subFdTable instanceof Table)){
            self::$subFdTable                                   = new Table(self::SF_TABLE_ROWS_MAX);
            self::$subFdTable->column(self::WS_FIELD_FD,                Table::TYPE_INT, 8);                                    // 网络fd
            self::$subFdTable->column(self::SK_FIELD_ID,                Table::TYPE_INT, 8);                                    // 订阅KeyId
            self::$subFdTable->create();
        }
        if(!(self::$ackNumTable instanceof Table)){
            self::$ackNumTable                                   = new Table(self::AN_TABLE_ROWS_MAX);
            self::$ackNumTable->column(self::AN_FIELD_EXPIRE_TIME,      Table::TYPE_INT, 8);                                    // 过期时间[秒]
            self::$ackNumTable->create();
        }
    }

    /**
     * @param int $fd
     * @return string
     * getWsConnTableKey
     */
    private static function getWsConnTableKey(int $fd):string
    {
        if($fd < 1) return '';
        return self::WS_TABLE_PREFIX.$fd;
    }

    /**
     * @param int $fd
     * @param int $time
     * @param string $userId
     * @param string $envStr
     * @param string $groupName
     * @param string $loginAppPlatform
     * @param string $loginDevId
     * @param string $loginKey
     * @param int $loginExpTime
     * @param string $loginAuth
     * @param string $langType
     * @return bool
     * saveWsConnTableData
     */
    public static function saveWsConnTableData(int $fd, int $time, string $userId, string $envStr, string $groupName, string $loginAppPlatform, string $loginDevId, string $loginKey, int $loginExpTime, string $loginAuth, string $langType):bool
    {
        if($fd < 1 || $time < 1 || strlen($userId) < 1 || strlen($userId) > self::WS_UID_LENGTH || strlen($envStr) > self::WS_ENV_LENGTH || strlen($groupName) > self::WS_GROUP_LENGTH) return false;
        return self::$wsConnTable->set(self::getWsConnTableKey($fd), [
            self::WS_FIELD_FD               => $fd,
            self::WS_FIELD_TIME             => $time,
            self::WS_FIELD_UID              => $userId,
            self::WS_FIELD_ENV              => $envStr,
            self::WS_FIELD_GROUP            => $groupName,
            self::WS_FIELD_LOGIN_PLATFORM   => $loginAppPlatform,
            self::WS_FIELD_LOGIN_DEVID      => $loginDevId,
            self::WS_FIELD_LOGIN_KEY        => $loginKey,
            self::WS_FIELD_LOGIN_EXP_TIME   => $loginExpTime,
            self::WS_FIELD_LOGIN_AUTH       => $loginAuth,
            self::WS_FIELD_LANG_TYPE        => $langType,
        ]);
    }

    /**
     * @param int $fd
     * @return bool
     * removeWsConnTableData
     */
    public static function removeWsConnTableData(int $fd):bool
    {
        self::clearSkTableData($fd);    // 清除订阅
        return self::$wsConnTable->del(self::getWsConnTableKey($fd)) || self::$wsConnTable->del(self::getWsConnTableKey($fd)) || self::$wsConnTable->del(self::getWsConnTableKey($fd));
    }

    /**
     * @return int
     * getWsConnTableRowNum
     */
    public static function getWsConnTableRowNum():int
    {
        return self::$wsConnTable->count();
    }

    /**
     * @param int $fd
     * @param int $time
     * @param string $userId
     * @param string $envStr
     * @param string $groupName
     * @param string $loginKey
     * @param int $loginExpTime
     * @param string $loginAppPlatform
     * @param string $loginDevId
     * @param string $loginAuth
     * @param string $langType
     * @return bool
     * getWsConnTableData
     */
    public static function getWsConnTableData(int $fd, int &$time, string &$userId, string &$envStr, string &$groupName, string &$loginKey, int &$loginExpTime, string &$loginAppPlatform, string &$loginDevId, string &$loginAuth, string &$langType):bool
    {
        if($fd < 1) return false;
        $wsConnTableData    = self::$wsConnTable->get(self::getWsConnTableKey($fd));
        if(!isset($wsConnTableData[self::WS_FIELD_FD]) || $wsConnTableData[self::WS_FIELD_FD] !== $fd || !isset($wsConnTableData[self::WS_FIELD_TIME]) || !isset($wsConnTableData[self::WS_FIELD_UID]) || !isset($wsConnTableData[self::WS_FIELD_ENV]) || !isset($wsConnTableData[self::WS_FIELD_GROUP]) || !isset($wsConnTableData[self::WS_FIELD_LOGIN_KEY]) || !isset($wsConnTableData[self::WS_FIELD_LOGIN_EXP_TIME]) || !isset($wsConnTableData[self::WS_FIELD_LOGIN_PLATFORM]) || !isset($wsConnTableData[self::WS_FIELD_LOGIN_DEVID]) || !isset($wsConnTableData[self::WS_FIELD_LOGIN_AUTH]) || !isset($wsConnTableData[self::WS_FIELD_LANG_TYPE])) return false;
        $time               = $wsConnTableData[self::WS_FIELD_TIME];
        $userId             = $wsConnTableData[self::WS_FIELD_UID];
        $envStr             = $wsConnTableData[self::WS_FIELD_ENV];
        $groupName          = $wsConnTableData[self::WS_FIELD_GROUP];
        $loginKey           = $wsConnTableData[self::WS_FIELD_LOGIN_KEY];
        $loginExpTime       = $wsConnTableData[self::WS_FIELD_LOGIN_EXP_TIME];
        $loginAppPlatform   = $wsConnTableData[self::WS_FIELD_LOGIN_PLATFORM];
        $loginDevId         = $wsConnTableData[self::WS_FIELD_LOGIN_DEVID];
        $loginAuth          = $wsConnTableData[self::WS_FIELD_LOGIN_AUTH];
        $langType           = $wsConnTableData[self::WS_FIELD_LANG_TYPE];
        return true;
    }

    /**
     * @param int $ackNum
     * @param int $expireTime
     * @return bool
     * setAckNum
     */
    public static function setAckNum(int $ackNum, int $expireTime):bool
    {
        return self::$ackNumTable->set(strval($ackNum), [self::AN_FIELD_EXPIRE_TIME => $expireTime]);
    }

    /**
     * @param int $ackNum
     * @return bool
     * delAckNum
     */
    public static function delAckNum(int $ackNum):bool
    {
        return self::$ackNumTable->del(strval($ackNum)) || self::$ackNumTable->del(strval($ackNum)) || self::$ackNumTable->del(strval($ackNum));
    }

    /**
     * @param int $ackNum
     * @param int $expireTime
     * @return bool
     * getAckNumExpireTime
     */
    public static function getAckNumExpireTime(int $ackNum, int &$expireTime):bool
    {
        $ackNumData     = self::$ackNumTable->get(strval($ackNum));
        if(!is_array($ackNumData) || !isset($ackNumData[self::AN_FIELD_EXPIRE_TIME]) || !is_int($ackNumData[self::AN_FIELD_EXPIRE_TIME])) return false;
        $expireTime     = $ackNumData[self::AN_FIELD_EXPIRE_TIME];
        return true;
    }

    /**
     * @return int
     * genAckNum
     */
    public static function genAckNum():int
    {
        $ackNum             = self::$configTable->incr(self::CF_KEY_ACK_NUM, self::CF_FIELD_NUM, 1);
        if($ackNum < 1000) $ackNum = self::$configTable->incr(self::CF_KEY_ACK_NUM, self::CF_FIELD_NUM, mt_rand(1000, 9999));
        if($ackNum > self::AN_TABLE_ROWS_MAX){
            if(!self::$configTable->set(self::CF_KEY_ACK_NUM, [self::CF_FIELD_NUM => mt_rand(1000, 9999)])) return 0;
            $ackNum         = self::$configTable->incr(self::CF_KEY_ACK_NUM, self::CF_FIELD_NUM, 1);
        }
        return $ackNum;
    }

    /**
     * @param string $subKey
     * @return string
     * getSubKeyTableKey
     */
    private static function getSubKeyTableKey(string $subKey):string
    {
        if(strlen($subKey) < 1) return '';
        return $subKey;
    }

    /**
     * @param string $subKey
     * @param int $newNum
     * @return int
     * genSubKeyId
     */
    private static function genSubKeyId(string $subKey, int $newNum = 0):int
    {
        $subKeyLength       = strlen($subKey);
        if($subKeyLength < 1 || $subKeyLength > self::SK_SUB_KEY_LENGTH || $newNum < 0) return 0;
        $subKeyTableKey     = self::getSubKeyTableKey($subKey);
        if(strlen($subKeyTableKey) < 1) return 0;
        $subKeyId           = self::$subKeyTable->get($subKeyTableKey, self::SK_FIELD_ID);
        if(is_int($subKeyId) && $subKeyId > 0) return $subKeyId;
        if(mt_rand(1, 100) >= 90) self::gcSubKeyTable();    // 10%概率触发GC
        $subKeyId           = self::$configTable->incr(self::CF_KEY_SUB_KEY_ID, self::CF_FIELD_NUM, 1);
        if(!self::$subKeyTable->set($subKeyTableKey, [self::SK_FIELD_ID => $subKeyId, self::SK_FIELD_NUM => $newNum])) return 0;
        return $subKeyId;
    }

    /**
     * @param string $subKey
     * @return int
     * getSubKeyId
     */
    private static function getSubKeyId(string $subKey):int
    {
        $subKeyLength       = strlen($subKey);
        if($subKeyLength < 1 || $subKeyLength > self::SK_SUB_KEY_LENGTH) return 0;
        $subKeyTableKey     = self::getSubKeyTableKey($subKey);
        if(strlen($subKeyTableKey) < 1) return 0;
        $subKeyId           = self::$subKeyTable->get($subKeyTableKey, self::SK_FIELD_ID);
        if(is_int($subKeyId) && $subKeyId > 0) return $subKeyId;
        return 0;
    }

    /**
     * @param array $subKeyIdList
     * @return array|string
     * getSubKeyListById
     */
    private static function getSubKeyListById(array $subKeyIdList):array
    {
        $subKeyList         = [];
        if(count($subKeyIdList) < 1 || self::$subKeyTable->count() < 1) return $subKeyList;
        $subKeyIdList       = array_unique($subKeyIdList);
        $subKeyIdListSize   = count($subKeyIdList);
        foreach (self::$subKeyTable as $subKeyTableKey => $subKeyTableVal){
            if(in_array($subKeyTableVal[self::SK_FIELD_ID], $subKeyIdList, true)){
                $subKeyList[]   = $subKeyTableKey;
                if(count($subKeyList) >= $subKeyIdListSize) break;
            }
        }
        return $subKeyList;
    }

    /**
     * @param int $subKeyId
     * @param int $decNum
     * @return void
     * decSubKeyNumById
     */
    private static function decSubKeyNumById(int $subKeyId, int $decNum):void
    {
        if($subKeyId < 1 || $decNum < 1) return;
        $subKeyTableRowNum  = self::$subKeyTable->count();
        if($subKeyTableRowNum < 1) return;
        foreach (self::$subKeyTable as $subKeyTableKey => $subKeyTableVal){
            if(isset($subKeyTableVal[self::SK_FIELD_ID]) && $subKeyTableVal[self::SK_FIELD_ID] === $subKeyId){
                $num    = self::$subKeyTable->decr($subKeyTableKey, self::SK_FIELD_NUM, $decNum);
                if($num < 1 && $subKeyTableRowNum > self::SK_TABLE_ROWS_GC_MIN) self::$subKeyTable->del($subKeyTableKey);
                break;
            }
        }
    }

    /**
     * @param string $subKey
     * @param int $decNum
     * @return void
     * decSubKeyNumByKey
     */
    private static function decSubKeyNumByKey(string $subKey, int $decNum):void
    {
        if(strlen($subKey) < 1 || $decNum < 1) return;
        $subKeyTableKey     = self::getSubKeyTableKey($subKey);
        if(strlen($subKeyTableKey) < 1) return;
        self::$subKeyTable->decr($subKeyTableKey, self::SK_FIELD_NUM, $decNum);
    }

    /**
     * @return void
     * gcSubKeyTable
     */
    private static function gcSubKeyTable():void
    {
        if(self::$subKeyTable->count() > self::SK_TABLE_ROWS_GC_MIN) foreach (self::$subKeyTable as $subKeyTableKey => $subKeyTableVal){
            if(isset($subKeyTableVal[self::SK_FIELD_NUM]) && $subKeyTableVal[self::SK_FIELD_NUM] > 0) continue;
            self::$subKeyTable->del($subKeyTableKey) || self::$subKeyTable->del($subKeyTableKey);
        }
    }

    /**
     * @param int $fd
     * @param string $subKey
     * @param int $newNum
     * @param int $subKeyId
     * @return string
     * getSubFdTableKey
     */
    private static function getSubFdTableKey(int $fd, string $subKey, int $newNum = 0, int &$subKeyId = 0):string
    {
        if($fd < 1 || strlen($subKey) < 1) return '';
        if($newNum < 1){
            $subKeyId   = self::getSubKeyId($subKey);
        }else{
            $subKeyId   = self::genSubKeyId($subKey, $newNum);
        }
        if($subKeyId < 1) return '';
        return $fd.'_'.$subKeyId;
    }

    /**
     * @param int $fd
     * @param array $subKeyList
     * @param array $subKeyFailList
     * @return bool
     * setSkTableData
     */
    public static function setSkTableData(int $fd, array $subKeyList, array &$subKeyFailList):bool
    {
        if($fd < 1) return false;
        self::clearSkTableData($fd);
        return !(count($subKeyList) > 0) || self::addSkTableData($fd, $subKeyList, $subKeyFailList);
    }

    /**
     * @param int $fd
     * @param array $subKeyList
     * @param array $subKeyFailList
     * @return bool
     * addSkTableData
     */
    public static function addSkTableData(int $fd, array $subKeyList, array &$subKeyFailList):bool
    {
        if($fd < 1) return false;
        if(count($subKeyList) < 1) return true;
        $result                 = true;
        foreach ($subKeyList as $subKey){
            $subKeyId           = 0;
            $subFdTableKey      = self::getSubFdTableKey($fd, $subKey, 1, $subKeyId);
            if($subKeyId < 1 || strlen($subFdTableKey) < 1){
                $result             = false;
                $subKeyFailList[]   = $subKey;
                continue;
            }
            if(!self::$subFdTable->set($subFdTableKey, [self::WS_FIELD_FD => $fd, self::SK_FIELD_ID => $subKeyId])){
                $result             = false;
                $subKeyFailList[]   = $subKey;
            }
        }
        return $result;
    }

    /**
     * @param int $fd
     * @param array $subKeyList
     * @param array $subKeyFailList
     * @return bool
     * delSkTableData
     */
    public static function delSkTableData(int $fd, array $subKeyList, array &$subKeyFailList):bool
    {
        if($fd < 1) return false;
        if(count($subKeyList) < 1) return true;
        $result                 = true;
        foreach ($subKeyList as $subKey){
            $subKeyId           = 0;
            $subFdTableKey      = self::getSubFdTableKey($fd, $subKey, 0, $subKeyId);
            if($subKeyId < 1 || strlen($subFdTableKey) < 1){
                $result             = false;
                $subKeyFailList[]   = $subKey;
                continue;
            }
            if(!self::$subFdTable->del($subFdTableKey)){
                $result             = false;
                $subKeyFailList[]   = $subKey;
            }else{
                self::decSubKeyNumByKey($subKey, 1);
            }
        }
        return $result;
    }

    /**
     * @param int $fd
     * @return bool
     * clearSkTableData
     */
    public static function clearSkTableData(int $fd):bool
    {
        if($fd < 1) return false;
        if(self::$subFdTable->count() < 1) return true;
        foreach (self::$subFdTable as $subFdTableKey => $subFdTableVal){
            if($subFdTableVal[self::WS_FIELD_FD] === $fd && (self::$subFdTable->del($subFdTableKey) || self::$subFdTable->del($subFdTableKey) || self::$subFdTable->del($subFdTableKey))){
                self::decSubKeyNumById($subFdTableVal[self::SK_FIELD_ID], 1);
            }
        }
        return true;
    }

    /**
     * @param int $fd
     * @param array $subKeyList
     * @return bool
     * getSkTableData
     */
    public static function getSkTableData(int $fd, array &$subKeyList):bool
    {
        if($fd < 1) return false;
        if(self::$subFdTable->count() < 1) return true;
        $subKeyIdList       = [];
        foreach (self::$subFdTable as $subFdTableVal){
            if($subFdTableVal[self::WS_FIELD_FD] === $fd) $subKeyIdList[] = $subFdTableVal[self::SK_FIELD_ID];
        }
        if(count($subKeyIdList) < 1) return true;
        $subKeyList         = self::getSubKeyListById($subKeyIdList);
        return true;
    }

    /**
     * @param array $subKeyList
     * @return array
     * getFdIdListBySubKey
     */
    public static function getFdIdListBySubKey(array $subKeyList):array
    {
//        $isDebugLog             = false;
//        if($isDebugLog){
//            $debugUserSubList   = [];
//            foreach (self::$wsConnTable as $wsConnTableVal){
//                $userIdItem     = $wsConnTableVal[self::WS_FIELD_UID];
//                $fdIdItem       = $wsConnTableVal[self::WS_FIELD_FD];
//                $debugUserSubList[$fdIdItem] = ['fd' => $fdIdItem, 'uId' => $userIdItem, 'skIdList' => []];
//            }
//            foreach (self::$subFdTable as $subFdTableVal){
//                $fdIdItem       = $subFdTableVal[self::WS_FIELD_FD];
//                $skIdItem       = $subFdTableVal[self::SK_FIELD_ID];
//                $debugUserSubList[$fdIdItem]['skIdList'][] = $skIdItem;
//            }
//        }
        $fdList             = [];
        if(count($subKeyList) < 1 || self::$subFdTable->count() < 1) return $fdList;
        $subKeyIdList       = [];
        foreach ($subKeyList as $subKey){
            $subKeyId       = self::getSubKeyId($subKey);
            if($subKeyId > 0) $subKeyIdList[] = $subKeyId;
        }
        if(count($subKeyIdList) < 1) return $fdList;
        $subKeyIdList       = array_unique($subKeyIdList);
        foreach (self::$subFdTable as $subFdTableVal){
            if(in_array($subFdTableVal[self::SK_FIELD_ID], $subKeyIdList, true)) $fdList[] = $subFdTableVal[self::WS_FIELD_FD];
        }
//        if($isDebugLog) Logs::trace("\n".CommString::getTableShellStr(array_values($debugUserSubList)).'skName: '.implode(',', $subKeyList).',skId: '.implode(',', $subKeyIdList).',fdId: '.implode(',', $fdList)."\n\n");
        return count($fdList) > 0 ? array_unique($fdList) : [];
    }

    /**
     * @param int $fd
     * @return string
     * getWsConnTableUserId
     */
    public static function getWsConnTableUserId(int $fd):string
    {
        if($fd < 1) return '';
        $userId             = self::$wsConnTable->get(self::getWsConnTableKey($fd), self::WS_FIELD_UID);
        return is_string($userId) ? $userId : '';
    }

    /**
     * @param int $fd
     * @return string
     * getWsConnTableGroup
     */
    public static function getWsConnTableGroup(int $fd):string
    {
        if($fd < 1) return '';
        $groupName          = self::$wsConnTable->get(self::getWsConnTableKey($fd), self::WS_FIELD_GROUP);
        return is_string($groupName) ? $groupName : '';
    }

    /**
     * @param string $userId
     * @return int
     * getWsConnTableFdId
     */
    public static function getWsConnTableFdId(string $userId):int
    {
        if(strlen($userId) < 1 || self::$wsConnTable->count() < 1) return 0;
        foreach (self::$wsConnTable as $tableData){
            if($tableData[self::WS_FIELD_UID] === $userId) return $tableData[self::WS_FIELD_FD];
        }
        return 0;
    }

    /**
     * @param array $userIdList
     * @return array
     * getWsConnTableFdIdList
     */
    public static function getWsConnTableFdIdList(array $userIdList):array
    {
        $fdList             = [];
        $userIdListLength   = count($userIdList);
        if($userIdListLength < 1 || self::$wsConnTable->count() < 1) return $fdList;
        foreach (self::$wsConnTable as $tableData){
            if(in_array($tableData[self::WS_FIELD_UID], $userIdList, true)){
                $fdList[$tableData[self::WS_FIELD_UID]] = $tableData[self::WS_FIELD_FD];
                if(count($fdList) >= $userIdListLength) break;
            }
        }
        return count($fdList) > 0 ? array_unique($fdList) : [];
    }

    /**
     * @return array
     * getAllFdIdList
     */
    public static function getAllFdIdList():array
    {
        $fdList             = [];
        if(self::$wsConnTable->count() < 1) return $fdList;
        foreach (self::$wsConnTable as $tableData){
            $fdList[]       = $tableData[self::WS_FIELD_FD];
        }
        return count($fdList) > 0 ? array_unique($fdList) : [];
    }

    /**
     * @param bool $isLogin
     * @return array
     * getFdIdListByToken
     */
    public static function getFdIdListByToken(bool $isLogin):array
    {
        $fdList             = [];
        if(self::$wsConnTable->count() < 1) return $fdList;
        foreach (self::$wsConnTable as $tableData){
            if($isLogin){
                if($tableData[self::WS_FIELD_UID] > 0) $fdList[] = $tableData[self::WS_FIELD_FD];
            }else{
                if($tableData[self::WS_FIELD_UID] < 1) $fdList[] = $tableData[self::WS_FIELD_FD];
            }
        }
        return count($fdList) > 0 ? array_unique($fdList) : [];
    }

    /**
     * @param array $platformList
     * @return array
     * getFdIdListByPlatform
     */
    public static function getFdIdListByPlatform(array $platformList):array
    {
        $fdList             = [];
        if(count($platformList) < 1 || self::$wsConnTable->count() < 1) return $fdList;
        foreach (self::$wsConnTable as $tableData){
            if(in_array($tableData[self::WS_FIELD_LOGIN_PLATFORM], $platformList, true)) $fdList[] = $tableData[self::WS_FIELD_FD];
        }
        return count($fdList) > 0 ? array_unique($fdList) : [];
    }

    /**
     * @param array $langList
     * @return array
     * getFdIdListByLang
     */
    public static function getFdIdListByLang(array $langList):array
    {
        $fdList             = [];
        if(count($langList) < 1 || self::$wsConnTable->count() < 1) return $fdList;
        foreach (self::$wsConnTable as $tableData){
            if(in_array($tableData[self::WS_FIELD_LANG_TYPE], $langList, true)) $fdList[] = $tableData[self::WS_FIELD_FD];
        }
        return count($fdList) > 0 ? array_unique($fdList) : [];
    }

    /**
     * @param array $devIdList
     * @return array
     * getFdIdListByDevId
     */
    public static function getFdIdListByDevId(array $devIdList):array
    {
        $fdList             = [];
        if(count($devIdList) < 1 || self::$wsConnTable->count() < 1) return $fdList;
        foreach (self::$wsConnTable as $tableData){
            if(in_array($tableData[self::WS_FIELD_LOGIN_DEVID], $devIdList, true)) $fdList[] = $tableData[self::WS_FIELD_FD];
        }
        return count($fdList) > 0 ? array_unique($fdList) : [];
    }

    /**
     * @param array $groupList
     * @return array
     * getFdIdListByGroup
     */
    public static function getFdIdListByGroup(array $groupList):array
    {
        $fdList             = [];
        if(count($groupList) < 1 || self::$wsConnTable->count() < 1) return $fdList;
        foreach (self::$wsConnTable as $tableData){
            if(in_array($tableData[self::WS_FIELD_GROUP], $groupList, true)) $fdList[] = $tableData[self::WS_FIELD_FD];
        }
        return count($fdList) > 0 ? array_unique($fdList) : [];
    }
}